package com.hvisions.config;

import com.google.common.base.MoreObjects;
import com.google.common.collect.Lists;
import com.hvisions.common.MyUaNode;
import com.hvisions.common.Tree;
import org.apache.logging.log4j.util.BiConsumer;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.UaClient;
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfig;
import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;
import org.eclipse.milo.opcua.sdk.client.model.nodes.objects.ServerTypeNode;
import org.eclipse.milo.opcua.sdk.client.nodes.UaNode;
//import org.eclipse.milo.opcua.stack.client.UaTcpStackClient;
import org.eclipse.milo.opcua.stack.core.AttributeId;
import org.eclipse.milo.opcua.stack.core.Identifiers;
import org.eclipse.milo.opcua.stack.core.UaException;
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
import org.eclipse.milo.opcua.stack.core.types.builtin.*;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
import org.eclipse.milo.opcua.stack.core.types.structured.Node;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author ykz
 */
@Component
public class OpcUaConfig {
    public static UaClient opcLink = null;

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Value("${opcUrl}")
    public String endPointUrl;

    /**
     * opc ua  打开连接订阅
     * res  是否需要重复连接
     *
     * @throws Exception
     */
    public void createSubscription(boolean res) {
        try {
            //等待三秒(可要可不要)
            Thread.sleep(3 * 1000);
            UaClient uaClient = null;

            Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "security");
            Files.createDirectories(securityTempDir);
            if (!Files.exists(securityTempDir)) {
                throw new Exception("unable to create security dir: " + securityTempDir);
            }
            OpcUaClient opcClient =  OpcUaClient.create(endPointUrl,
                    endpoints ->
                            endpoints.stream()
                                    .filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri()))
                                    .findFirst(),
                    configBuilder ->
                            configBuilder
                                    .setApplicationName(LocalizedText.english("eclipse milo opc-ua client"))
                                    .setApplicationUri("urn:eclipse:milo:examples:client")
                                    //访问方式
                                    .setIdentityProvider(new AnonymousProvider())
                                    .setRequestTimeout(UInteger.valueOf(50000))
                                    .build()
            );
            uaClient = opcClient.connect().get();
            if (opcLink == null){
                opcLink = uaClient;
            }else {
                uaClient.disconnect().get();
            }
            logger.info("opc链接成功");
        } catch (Exception e) {
            if (res) {
                //重复连接
                logger.info("opc重复连接！");
                createSubscription(true);
            } else {
                System.out.println(e.getMessage());
            }
        }
    }


    /**
     * 遍历树形节点
     *
     * @param uaNode 节点
     * @throws Exception
     */
    public static Tree<UaNode> browseNodeTree(UaNode uaNode) throws Exception {
        List<? extends UaNode> nodes;
        Tree<UaNode> tree = new Tree<>(uaNode);
        if (uaNode == null) {
            nodes = opcLink.getAddressSpace().browseNodes(Identifiers.ObjectsFolder);
        } else {
            nodes = opcLink.getAddressSpace().browseNodes(uaNode);
        }
        for (UaNode nd : nodes) {
            tree.addChilds(browseNodeTree(nd));
//            tree.addChilds(nodes);
        }
        return tree;
    }

    public static List<? extends UaNode> browseNode(UaNode uaNode) throws Exception {
        List<? extends UaNode> nodes;
        if (uaNode == null) {
            nodes = opcLink.getAddressSpace().browseNodes(Identifiers.ObjectsFolder);
        } else {
            nodes = opcLink.getAddressSpace().browseNodes(uaNode);
        }
        nodes = nodes.stream().filter(t->!(t instanceof ServerTypeNode || t.getBrowseName().getName().startsWith("_"))).collect(Collectors.toList());
        return nodes;
    }

    /**
     * 读取节点数据
     *
     * @throws Exception
     */
    public static Object readNode(String key) throws Exception {
        int namespaceIndex = 2;
        //节点
        NodeId nodeId = new NodeId(namespaceIndex, key);
        //读取节点数据
        DataValue value = opcLink.readValue(0.0, TimestampsToReturn.Neither, nodeId).get();
        //标识符
        String identifier1 = String.valueOf(nodeId.getIdentifier());
        return value.getValue().getValue();
    }

    /**
     * 写入节点数据
     *
     * @param client
     * @throws Exception
     */
    public static boolean writeNodeValue(UaClient client,String key ,String value,String valueType) {
        //节点
        NodeId nodeId = new NodeId(2, key);
        //创建数据对象,此处的数据对象一定要定义类型，不然会出现类型错误，导致无法写入
        Object v = value;
        switch (valueType){
            case "short":
                v = Short.valueOf(value);
                break;
            case "int":
            case "long":
                v = Integer.valueOf(value);
                break;
            case "float":
                v = Float.valueOf(value);
                break;
            case "double":
                v = Double.valueOf(value);
                break;
            case "bool":
                v = value.equals("1")? Boolean.valueOf("true"):Boolean.valueOf("false");
                break;
            case "string":
                v = String.valueOf(value);
                break;
            case "word":
                v = Unsigned.ushort(value);
                break;
            default:
                v = String.valueOf(v);
                break;
        }
        DataValue nowValue = new DataValue(new Variant(v), null, null);
        //写入节点数据
        StatusCode statusCode = client.writeValue(nodeId, nowValue).join();
        return statusCode.isGood();
    }

    public Boolean exist(List<String> list1, List<String> list2) {
        Collections.sort(list1);
        Collections.sort(list2);
        return list1.equals(list2);
    }
}